package com.tenement.service.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.ObjectUtil;
import com.mybatisflex.core.query.QueryMethods;
import com.mybatisflex.core.query.QueryWrapper;
import com.mybatisflex.spring.service.impl.ServiceImpl;
import com.tenement.common.GlobalException;
import com.tenement.common.RedisConConstant;
import com.tenement.dto.SelectDto;
import com.tenement.entity.Resolution;
import com.tenement.entity.Vote;
import com.tenement.enums.RespBusiness;
import com.tenement.mapper.VoteMapper;
import com.tenement.service.ResolutionService;
import com.tenement.service.VoteService;
import com.tenement.utils.RedisCache;
import com.tenement.vo.VoteVo;
import lombok.RequiredArgsConstructor;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.tenement.entity.table.VoteTableDef.VOTE;

/**
 *  服务层实现。
 *
 * @author BertXie
 * @since 1.0.1
 */
@Service
@RequiredArgsConstructor
public class VoteServiceImpl extends ServiceImpl<VoteMapper, Vote> implements VoteService {

    private final VoteMapper voteMapper;

    private final ResolutionService resolutionService;

    private final RedissonClient redissonClient;

    private final RedisCache redisCache;

    @Override
    public List<SelectDto> infoCount(Long resolutionId) {
        QueryWrapper queryWrapper = QueryWrapper.create().from(VOTE)
                .select(VOTE.OPTIONS.as("label"), QueryMethods.count(VOTE.OPTIONS).as("value"))
                .where(VOTE.RESOLUTION_ID.eq(resolutionId))
                .groupBy(VOTE.OPTIONS);
        return voteMapper.selectListByQueryAs(queryWrapper, SelectDto.class);
    }

    @Override
    public int removeByResolutionIds(List<Long> resolutionIds) {
        return voteMapper.deleteByQuery(QueryWrapper.create().from(VOTE).where(VOTE.RESOLUTION_ID.in(resolutionIds)));
    }


    @Override
    public boolean vote(VoteVo voteVo) {
        RLock lock = redissonClient.getLock(RedisConConstant.RESOLUTION_VOTE_USER_LOCK + voteVo.getUserId());
        try {
            boolean tryLock = lock.tryLock(20, TimeUnit.SECONDS);
            if (tryLock) {
                Resolution resolution = resolutionService.usable(voteVo.getResolutionId());
                if (ObjectUtil.isNotNull(resolution)) {
                    // 检查当前时间是否在投票开始时间和结束时间之间,如果当前时间早于投票开始时间或晚于投票结束时间,则抛出无法投票的
                    if (DateUtil.compare(new Date(), resolution.getStartTime()) < 0 || DateUtil.compare(new Date(), resolution.getEndTime()) > 0) {
                        throw new GlobalException(RespBusiness.NO_VOTE);
                    }
                    Short dec = resolution.getDec();
                    //只能投一次
                    if (dec == 0) {
                        //多选
                        if(resolution.getMultiple() == 0) {
                            long voted = checkMyVote(voteVo, false);
                            if (voted + voteVo.getOption().size() <= resolution.getAtMost()) {
                                List<Vote> votes = voteVo.getOption().stream().map(option -> new Vote().setOptions(option).setUserId(voteVo.getUserId()).setResolutionId(voteVo.getResolutionId())).collect(Collectors.toList());
                                return this.saveBatch(votes);
                            }
                            throw new GlobalException(RespBusiness.VOTE_LIMIT);
                        }else {
                            if (redisCache.isMember(RedisConConstant.RESOLUTION_VOTE_USER + voteVo.getResolutionId(), voteVo.getUserId())) {
                                throw new GlobalException(RespBusiness.VOTE_LIMIT);
                            }
                            redisCache.setSetVal(RedisConConstant.RESOLUTION_VOTE_USER + voteVo.getResolutionId(), voteVo.getUserId());
                            return voteMapper.insert(new Vote()
                                    .setOptions(voteVo.getOption().get(0))
                                    .setResolutionId(voteVo.getResolutionId())
                                    .setUserId(voteVo.getUserId())
                            ) > 0;
                        }
                    }
                    Date date = new Date();
                    SimpleDateFormat format = new SimpleDateFormat("yyyy:MM:dd");
                    if(resolution.getMultiple() == 0) {
                        String key = RedisConConstant.RESOLUTION_VOTE_DEC + format.format(date) + ":" + voteVo.getResolutionId() + ":" + voteVo.getUserId();
                        long votedCount = redisCache.getIncrementVal(key);
                        if (votedCount + voteVo.getOption().size() <= resolution.getAtMost()) {
                            List<Vote> votes = voteVo.getOption().stream().map(option -> new Vote().setOptions(option).setUserId(voteVo.getUserId()).setResolutionId(voteVo.getResolutionId())).collect(Collectors.toList());
                            redisCache.setIncrement(key,votes.size());
                            redisCache.setExpire(key, 1, TimeUnit.DAYS);
                            return this.saveBatch(votes);
                        }
                        throw new GlobalException(RespBusiness.DAY_VOTE_LIMIT);
                    }
                    String userKey = RedisConConstant.RESOLUTION_VOTE_USER_DEC + format.format(date) + ":" + voteVo.getResolutionId();
                    if (redisCache.isMember(userKey, voteVo.getUserId())) {
                        throw new GlobalException(RespBusiness.VOTE_LIMIT);
                    }
                    redisCache.setSetVal(userKey, voteVo.getUserId());
                    redisCache.setExpire(userKey, 1, TimeUnit.DAYS);
                    return voteMapper.insert(new Vote()
                            .setOptions(voteVo.getOption().get(0))
                            .setResolutionId(voteVo.getResolutionId())
                            .setUserId(voteVo.getUserId())) > 0;
                }
                throw new GlobalException(RespBusiness.NO_RESOLUTION);
            }
            throw new GlobalException(RespBusiness.FREQUENT_OPERATION);
        } catch (InterruptedException e) {
            throw new GlobalException(RespBusiness.THREAD_INTERRUPT);
        } finally {
            lock.unlock();
        }

    }

    private long checkMyVote(VoteVo voteVo, boolean isDay) {
        return voteMapper.selectCountByQuery
                (
                    QueryWrapper.create()
                    .from(VOTE)
                    .where(VOTE.RESOLUTION_ID.eq(voteVo.getResolutionId()))
                    .and(VOTE.USER_ID.eq(voteVo.getUserId()))
                    .between("create_time", DateUtil.beginOfDay(new Date()) , DateUtil.endOfDay(new Date()) ,isDay)
                );
    }

}
